
package ru.spb.nicetu.altx.mnemo.utils.xmlsettings;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;

import org.w3c.dom.Attr;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;


abstract public class ABaseDomSettingsDocument implements ISettingsDocument {

  protected Document m_document;

  private static final String EMPTY_STRING = null;

  public ABaseDomSettingsDocument(Document doc) {
    super();
    m_document = doc;
  }

  @Override
  public boolean addElement(String groupId, String groupType, String elementId,
                            Map<String, String> properties) {
    boolean result = false;

    NodeList schema = m_document.getElementsByTagName(ROOT_TAG_NAME);
    if (schema.getLength() != 1)
      return result;
    Node rootNode = schema.item(0);

    Node groupNode = getGroupNode(rootNode, groupId, groupType);
    if (groupNode == null)
      return false;
    Node elementNode = getElementNode(groupNode, elementId);
    if (elementNode == null) {
      Element elementElement = m_document.createElement(ELEMENT_TAG_NAME);
      Attr attribute = m_document.createAttribute(KEY_ATTRIBUTE_NAME);
      attribute.setNodeValue(elementId);
      elementElement.setAttributeNode(attribute);
      if (properties != null) {
        for (String key : properties.keySet()) {
          String value = properties.get(key);
          setNodeProperty(elementElement, key, value);
        }
      }
      groupNode.appendChild(elementElement);
    } else {
      return false;
    }

    updateSchemaFile();

    return true;
  }

  @Override
  public boolean addGroup(String groupId, String type,
                          Map<String, String> properties) {
    boolean result = false;

    NodeList schema = m_document.getElementsByTagName(ROOT_TAG_NAME);
    if (schema.getLength() != 1)
      return result;
    Node rootNode = schema.item(0);

    Node groupNode = getGroupNode(rootNode, groupId, type);
    if (groupNode == null) {
      Element groupElement = m_document.createElement(GROUP_TAG_NAME);
      Attr attribute = m_document.createAttribute(NAME_ATTRIBUTE_NAME);
      attribute.setNodeValue(groupId);
      groupElement.setAttributeNode(attribute);

      groupElement.setAttribute(TYPE_ATTRIBUTE_NAME, type);
      if (properties != null) {
        for (String key : properties.keySet()) {
          String value = properties.get(key);
          setNodeProperty(groupElement, key, value);
        }
      }
      rootNode.appendChild(groupElement);
    } else {
      return false;
    }
    updateSchemaFile();
    return true;
  }

  @Override
  public String getElementProperty(String groupId, String groupType,
                                   String elementId, String key) {
    String result = EMPTY_STRING;

    NodeList schema = m_document.getElementsByTagName(ROOT_TAG_NAME);
    if (schema.getLength() != 1)
      return result;
    Node rootNode = schema.item(0);

    Node groupNode = getGroupNode(rootNode, groupId, groupType);
    Node element = getElementNode(groupNode, elementId);

    if (element == null) {
      return result;
    } else {
      result = getPropertyNode(element, key).getAttributes().getNamedItem(
          VALUE_ATTRIBUTE_NAME).getNodeValue();
    }
    return result;
  };


  public Collection<String> getGroupElements(String groupId, String groupType) {
    Collection<String> result = new ArrayList<String>();
    NodeList schema = m_document.getElementsByTagName(ROOT_TAG_NAME);
    if (schema.getLength() != 1)
      return result;
    Node rootNode = schema.item(0);

    Node groupNode = getGroupNode(rootNode, groupId, groupType);
    if (groupNode == null)
      return result;

    NodeList children = groupNode.getChildNodes();
    for (int i = 0; i < children.getLength(); i++) {
      Node child = children.item(i);
      if (!child.getNodeName().equals(ELEMENT_TAG_NAME))
        continue;

      result.add(child.getAttributes().getNamedItem(KEY_ATTRIBUTE_NAME)
          .getNodeValue());
    }
    return result;
  }

  @Override
  public String getGroupProperty(String groupId, String key) {
    // TODO
    String result = EMPTY_STRING;

    NodeList schema = m_document.getElementsByTagName(ROOT_TAG_NAME);
    if (schema.getLength() != 1)
      return result;
    Node rootNode = schema.item(0);

    Node groupNode = getGroupNode(rootNode, groupId, key);
    if (groupNode != null) {
      Node propertyNode = getPropertyNode(groupNode, key);
      result = propertyNode.getAttributes().getNamedItem(VALUE_ATTRIBUTE_NAME)
          .getNodeValue();
    }

    return result;
  }

  @Override
  public Collection<String> getGroups(String groupType) {
    Collection<String> result = new ArrayList<String>();
    NodeList schema = m_document.getElementsByTagName(ROOT_TAG_NAME);
    if (schema.getLength() != 1)
      return result;
    Node rootNode = schema.item(0);

    NodeList children = rootNode.getChildNodes();
    for (int i = 0; i < children.getLength(); i++) {
      Node child = children.item(i);
      if (!child.getNodeName().equals(GROUP_TAG_NAME))
        continue;

      if (child.getAttributes().getNamedItem(TYPE_ATTRIBUTE_NAME)
          .getNodeValue().equals(groupType))
        result.add(child.getAttributes().getNamedItem(NAME_ATTRIBUTE_NAME)
            .getNodeValue());
    }
    return result;
  }

  @Override
  public boolean isGroupExist(String groupId, String groupType) {
    NodeList schema = m_document.getElementsByTagName(ROOT_TAG_NAME);
    if (schema.getLength() != 1)
      return false;
    Node rootNode = schema.item(0);

    Node groupNode = getGroupNode(rootNode, groupId, groupType);
    if (groupNode == null)
      return false;
    else
      return true;
  }

  @Override
  public boolean removeElement(String groupId, String groupType, String elementId) {
    boolean result = false;

    NodeList schema = m_document.getElementsByTagName(ROOT_TAG_NAME);
    if (schema.getLength() != 1)
      return false;
    Node rootNode = schema.item(0);

    Node groupNode = getGroupNode(rootNode, groupId, groupType);
    if (groupNode == null)
      return true;
    else {
      Node elementNode = getElementNode(groupNode, elementId);
      if (elementNode == null)
        return true;
      try {
        groupNode.removeChild(elementNode);
      } catch (DOMException e) {
        return false;
      }
      result = true;
      updateSchemaFile();
      return result;
    }
  }

  @Override
  public boolean removeGroup(String groupId, String groupType) {
    boolean result = false;

    NodeList schema = m_document.getElementsByTagName(ROOT_TAG_NAME);
    if (schema.getLength() != 1)
      return false;
    Node rootNode = schema.item(0);

    Node groupNode = getGroupNode(rootNode, groupId, groupType);
    if (groupNode == null)
      return true;
    else {
      try {
        rootNode.removeChild(groupNode);
      } catch (DOMException e) {
        return false;
      }
      result = true;
      updateSchemaFile();
      return result;
    }
  }


  @Override
  public void setElementProperty(String groupId, String groupType,
                                 String elementId, String key, String value) {

    NodeList schema = m_document.getElementsByTagName(ROOT_TAG_NAME);
    if (schema.getLength() != 1)
      return;
    Node rootNode = schema.item(0);

    Node groupNode = getGroupNode(rootNode, groupId, groupType);
    Node element = getElementNode(groupNode, elementId);

    setNodeProperty(element, key, value);

    updateSchemaFile();
  };

  @Override
  public void setGroupProperty(String groupId, String groupType, String key,
                               String value) {
    // XXX
    NodeList schema = m_document.getElementsByTagName(ROOT_TAG_NAME);

    if (schema.getLength() != 1)
      return;
    Node schemaNode = schema.item(0);

    Node groupNode = getGroupNode(schemaNode, groupId, groupType);
    setNodeProperty(groupNode, key, value);

    updateSchemaFile();
  }

  @Override
  abstract public InputStream toStream();

  /**
   * Обновить ресурсы при узменении содержимого
   */
  abstract protected void updateSchemaFile();

  private void setNodeProperty(Node parentNode, String key, String value) {
    Node propertyNode = getPropertyNode(parentNode, key);

    if (propertyNode != null) {
      propertyNode.getAttributes().getNamedItem(VALUE_ATTRIBUTE_NAME)
          .setNodeValue(value);
    } else {
      Element newPropertyNode = m_document.createElement(PROPERTY_TAG_NAME);
      newPropertyNode.setAttribute(KEY_ATTRIBUTE_NAME, key);
      newPropertyNode.setAttribute(VALUE_ATTRIBUTE_NAME, value);

      parentNode.appendChild(newPropertyNode);
    }
  }

  /**
   *
   * @param parentNode
   *          - узел-родитель
   * @param groupId
   *          - ключь-идентификатор группы
   * @return - узел-группу, имеющий идентификатор {@code key}
   */
  private static Node getGroupNode(Node parentNode, String groupId, String type) {
    Node result = null;

    NodeList children = parentNode.getChildNodes();

    for (int i = 0; i < children.getLength(); i++) {
      Node node = children.item(i);
      if (!node.getNodeName().equals(GROUP_TAG_NAME))
        continue;

      NamedNodeMap attributes = node.getAttributes();
      String oKey = attributes.getNamedItem(NAME_ATTRIBUTE_NAME).getNodeValue();
      if (oKey.equals(groupId)) {
        oKey = attributes.getNamedItem(TYPE_ATTRIBUTE_NAME).getNodeValue();
        if (oKey.equals(type)) {
          result = node;
          break;
        }
      }
    }
    return result;
  }

  /**
   *
   * @param groupNode
   *          - Узел-группа
   * @param elementId
   *          - Идентификатор элемента
   * @return - дочерний узел, принадлежащий {@code groupNode }, и имеющий
   *         аттрибут {@code id} равный elementId
   */
  private static Node getElementNode(Node groupNode, String elementId) {
    Node result = null;

    NodeList children = groupNode.getChildNodes();

    for (int i = 0; i < children.getLength(); i++) {
      Node node = children.item(i);

      if (!node.getNodeName().equals(ELEMENT_TAG_NAME))
        continue;

      NamedNodeMap attributes = node.getAttributes();
      String oKey = attributes.getNamedItem(KEY_ATTRIBUTE_NAME).getNodeValue();
      if (oKey.equals(elementId)) {
        result = node;
        break;
      }
    }
    return result;
  }
//
  /**
   *
   * @param parentNode
   *          - узел-родитель
   * @param key
   *          - ключ параметра
   * @return возвращает значение параметра узла {@code parentNode} по ключу
   *         {@code key}
   */
  private static Node getPropertyNode(Node parentNode, String key) {
    Node result = null;

    NodeList schemaChildren = parentNode.getChildNodes();

    for (int idx = 0; idx < schemaChildren.getLength(); idx++) {
      Node node = schemaChildren.item(idx);
      if (!node.getNodeName().equals(PROPERTY_TAG_NAME))
        continue;
      NamedNodeMap attributes = node.getAttributes();

      Node attribute = attributes.getNamedItem(KEY_ATTRIBUTE_NAME);
      if (attribute.getNodeValue().equals(key)){
        result = node;
        break;
      }
    }

    return result;
  }

}
